from enum import Enum
import os
import datetime
import logging
import random
import numpy as np
import pandas as pd


class TradeType(Enum):
    LONGOPEN = 'LONGOPEN'
    LONGCLOSE = 'LONGCLOSE'
    SHORTOPEN = 'SHORTOPEN'
    SHORTCLOSE = 'SHORTCLOSE'


class Uninstantiable:

    def __init__(self) -> None:
        raise Exception('This class is un-instantiable.')


class BaseBase(Uninstantiable):

    _daysClosed: list[datetime.date] = []
    _isCloseOfDay = False

    def init() -> None:
        raise NotImplementedError

    def update() -> bool:
        raise NotImplementedError

    def now() -> pd.Timestamp:
        raise NotImplementedError

    def isCloseOfDay() -> bool:
        return BaseBase._isCloseOfDay


class BaseRealtimeTQ(BaseBase):

    def init(brokerID='', accountID='', password='',
             authName='', authPass='',
             paperTrading: bool = True) -> None:
        '''
            brokerID, accountID, password: 实盘信息
            authName, authPass: 信易认证信息
        '''
        from tqsdk import TqApi, TqKq, TqAccount, TqAuth
        account = None
        if paperTrading:
            account = TqKq()
        else:
            account = TqAccount(brokerID, accountID, password)
        BaseRealtimeTQ.tqApi = TqApi(account=account,
                                     auth=TqAuth(authName, authPass))

    def update() -> bool:
        return BaseRealtimeTQ.tqApi.wait_update()

    def now() -> pd.Timestamp:
        return pd.Timestamp(datetime.datetime.now())


class BaseBacktest(BaseBase):

    _isFirstUpdate = False

    def init(clock: pd.DatetimeIndex, progressSects: int = 10) -> None:
        BaseBacktest.clock = clock.copy()
        BaseBacktest._timing: int = -1
        BaseBacktest._maxTiming: int = \
            BaseBacktest.clock.get_loc(BaseBacktest.clock[-1])
        BaseBacktest.progressSects = progressSects
        BaseBacktest._sectTimings = \
            np.linspace(start=0,
                        stop=BaseBacktest._maxTiming,
                        num=BaseBacktest.progressSects+1)

    def update() -> bool:
        BaseBacktest._timing += 1
        if BaseBacktest._timing >= 0 and \
                BaseBacktest._timing <= BaseBacktest._maxTiming:

            if BaseBacktest._timing == 0:
                BaseBacktest._isFirstUpdate = True
            else:
                BaseBacktest._isFirstUpdate = False

            now = BaseBacktest.now()
            nowDate = now.date()
            nowTime = now.time()
            if nowDate not in BaseBase._daysClosed and \
                    nowTime >= datetime.time(15, 0) and \
                    nowTime < datetime.time(15, 1):
                BaseBase._daysClosed.append(nowDate)
                BaseBase._isCloseOfDay = True
            else:
                BaseBase._isCloseOfDay = False

            BaseBacktest._printProgress(BaseBacktest._timing)
            return True
        else:
            return False

    def now() -> pd.Timestamp:
        return BaseBacktest.clock[BaseBacktest._timing]

    def isFirstUpdate() -> bool:
        return BaseBacktest._isFirstUpdate

    def _printProgress(timing: int) -> None:
        sectTimings = BaseBacktest._sectTimings
        if timing >= sectTimings[0]:
            progressSects = BaseBacktest.progressSects
            print('%d/%d completed' %
                  (progressSects + 1 - len(sectTimings), progressSects))
            BaseBacktest._sectTimings = np.delete(sectTimings, [0])


Base = BaseBacktest


def setBaseRealtime() -> None:
    global Base
    Base = BaseRealtimeTQ


class Log(Uninstantiable):

    logDir = './log/'
    logName = 'base'
    # logFormat = '[%(asctime)s %(levelname)s] %(message)s'
    logFormat = '%(levelname)s %(message)s'

    def init(logName='') -> None:
        logPath = Log.logDir
        if not os.path.exists(logPath):
            os.makedirs(logPath)
        if logName:
            logPath += logName
        else:
            logPath += Log.logName
        logPath += '.log'
        logging.basicConfig(filename=logPath, filemode='w', encoding='utf-8',
                            format=Log.logFormat,
                            level=logging.DEBUG)

    def _logsString(log: str) -> str:
        return '%s: %s' % (Base.now().strftime('%Y-%m-%d %H:%M:%S'), log)

    def debug(log: str) -> None:
        logging.debug(Log._logsString(log))

    def info(log: str) -> None:
        logging.info(Log._logsString(log))

    def warning(log: str) -> None:
        logging.warning(Log._logsString(log))

    def error(log: str) -> None:
        logging.error(Log._logsString(log))


def generateID(length: int = 8) -> str:
    id = ''
    letters = \
        'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
    lettersLen = len(letters) - 1
    for i in range(length):
        id += letters[random.randint(0, lettersLen)]
    return id
